<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>OpenLayers 矢量点位动态控制</title>
  <link rel="stylesheet"
    href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/css/ol.css">
  <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/build/ol.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
      color: #fff;
      min-height: 100vh;
      padding: 20px;
    }

    .container {
      max-width: 1400px;
      margin: 0 auto;
      display: flex;
      flex-direction: column;
      gap: 20px;
    }

    header {
      text-align: center;
      padding: 20px;
      background: rgba(0, 0, 0, 0.3);
      border-radius: 15px;
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.4);
    }

    h1 {
      font-size: 2.5rem;
      margin-bottom: 10px;
      background: linear-gradient(45deg, #ff8a00, #e52e71);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }

    .subtitle {
      font-size: 1.1rem;
      color: #d1d1d1;
      max-width: 800px;
      margin: 0 auto;
      line-height: 1.6;
    }

    .content {
      display: flex;
      gap: 20px;
      height: 650px;
    }

    .map-container {
      flex: 3;
      position: relative;
      border-radius: 15px;
      overflow: hidden;
      box-shadow: 0 8px 25px rgba(0, 0, 0, 0.5);
    }

    #map {
      width: 100%;
      height: 100%;
    }

    .controls {
      flex: 1;
      background: rgba(0, 0, 0, 0.3);
      border-radius: 15px;
      padding: 25px;
      box-shadow: 0 8px 25px rgba(0, 0, 0, 0.5);
      display: flex;
      flex-direction: column;
      gap: 20px;
    }

    .control-section {
      background: rgba(255, 255, 255, 0.1);
      border-radius: 12px;
      padding: 20px;
      backdrop-filter: blur(10px);
    }

    .section-title {
      font-size: 1.3rem;
      margin-bottom: 15px;
      padding-bottom: 10px;
      border-bottom: 2px solid rgba(255, 255, 255, 0.2);
      color: #ff8a00;
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .section-title i {
      font-size: 1.5rem;
    }

    .layer-controls {
      display: flex;
      flex-direction: column;
      gap: 15px;
    }

    .layer-item {
      display: flex;
      align-items: center;
      padding: 12px 15px;
      background: rgba(0, 0, 0, 0.2);
      border-radius: 8px;
      transition: all 0.3s ease;
      cursor: pointer;
    }

    .layer-item:hover {
      background: rgba(255, 255, 255, 0.15);
      transform: translateX(5px);
    }

    .layer-item input {
      margin-right: 15px;
      width: 20px;
      height: 20px;
      cursor: pointer;
    }

    .layer-color {
      width: 25px;
      height: 25px;
      border-radius: 50%;
      margin-right: 15px;
      border: 2px solid rgba(255, 255, 255, 0.5);
    }

    .layer-info {
      flex: 1;
    }

    .layer-name {
      font-weight: 600;
      font-size: 1.1rem;
      margin-bottom: 3px;
    }

    .layer-count {
      font-size: 0.85rem;
      color: #aaa;
    }

    .legend {
      display: flex;
      flex-wrap: wrap;
      gap: 15px;
      margin-top: 10px;
    }

    .legend-item {
      display: flex;
      align-items: center;
      gap: 8px;
      background: rgba(0, 0, 0, 0.2);
      padding: 8px 12px;
      border-radius: 8px;
      font-size: 0.9rem;
    }

    .legend-color {
      width: 18px;
      height: 18px;
      border-radius: 50%;
      border: 1px solid rgba(255, 255, 255, 0.5);
    }

    .feature-info {
      margin-top: 15px;
      padding: 15px;
      background: rgba(0, 0, 0, 0.2);
      border-radius: 8px;
      max-height: 150px;
      overflow-y: auto;
    }

    .feature-info h3 {
      font-size: 1.1rem;
      margin-bottom: 10px;
      color: #ff8a00;
    }

    .feature-info p {
      font-size: 0.9rem;
      line-height: 1.5;
    }

    footer {
      text-align: center;
      padding: 20px;
      font-size: 0.9rem;
      color: #aaa;
      background: rgba(0, 0, 0, 0.3);
      border-radius: 15px;
      margin-top: 10px;
    }

    .status-bar {
      position: absolute;
      bottom: 15px;
      left: 15px;
      background: rgba(0, 0, 0, 0.7);
      padding: 8px 15px;
      border-radius: 8px;
      font-size: 0.9rem;
      z-index: 1000;
    }

    .map-overlay {
      position: absolute;
      top: 15px;
      right: 15px;
      background: rgba(0, 0, 0, 0.7);
      padding: 10px;
      border-radius: 8px;
      z-index: 1000;
      max-width: 300px;
    }

    @media (max-width: 900px) {
      .content {
        flex-direction: column;
        height: auto;
      }

      .map-container {
        height: 500px;
      }
    }
  </style>
</head>

<body>
  <div class="container">
    <header>
      <h1>OpenLayers 矢量点位动态控制</h1>
      <p class="subtitle">本示例演示如何在地图上添加不同类型的矢量点位，并实现动态控制点位的显示与隐藏。使用图层分组和复选框控制，可以灵活切换各类点位的可见性。</p>
    </header>

    <div class="content">
      <div class="map-container">
        <div id="map"></div>
        <div class="status-bar" id="status">坐标: 正在加载...</div>
        <div class="map-overlay">
          <div class="legend">
            <div class="legend-item">
              <div class="legend-color" style="background-color: #ff4757;"></div>
              <span>餐厅</span>
            </div>
            <div class="legend-item">
              <div class="legend-color" style="background-color: #2ed573;"></div>
              <span>公园</span>
            </div>
            <div class="legend-item">
              <div class="legend-color" style="background-color: #1e90ff;"></div>
              <span>酒店</span>
            </div>
            <div class="legend-item">
              <div class="legend-color" style="background-color: #ffa502;"></div>
              <span>商场</span>
            </div>
            <div class="legend-item">
              <div class="legend-color" style="background-color: #a55eea;"></div>
              <span>医院</span>
            </div>
          </div>
        </div>
      </div>

      <div class="controls">
        <div class="control-section">
          <div class="section-title">
            <span>图层控制</span>
          </div>
          <div class="layer-controls" id="layer-controls">
            <!-- 图层控制项将通过JS动态生成 -->
          </div>
        </div>

        <div class="control-section">
          <div class="section-title">
            <span>点位信息</span>
          </div>
          <div class="feature-info" id="feature-info">
            <h3>交互提示</h3>
            <p>点击地图上的点位查看详细信息。使用左侧图层控制可以切换不同类型点位的显示状态。</p>
          </div>
        </div>
      </div>
    </div>

    <footer>
      <p>OpenLayers 矢量点位控制示例 | 使用 OpenLayers 6 | 动态图层控制实现</p>
    </footer>
  </div>

  <script>
    // 初始化地图
    const map = new ol.Map({
      target: 'map',
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        })
      ],
      view: new ol.View({
        center: ol.proj.fromLonLat([116.4074, 39.9042]), // 北京中心
        zoom: 11
      })
    });

    // 状态栏显示坐标
    map.on('pointermove', function (evt) {
      const coordinate = ol.proj.toLonLat(evt.coordinate);
      document.getElementById('status').textContent =
        `坐标: ${coordinate[0].toFixed(6)}, ${coordinate[1].toFixed(6)}`;
    });

    // 图层定义 - 不同类型点位
    const layerGroups = [
      {
        id: 'restaurants',
        name: '餐厅',
        color: '#ff4757',
        visible: true,
        features: [
          { name: '北京烤鸭店', lon: 116.4038, lat: 39.9151, info: '正宗北京烤鸭，提供传统宫廷菜' },
          { name: '川菜馆', lon: 116.4082, lat: 39.9087, info: '麻辣鲜香的四川风味' },
          { name: '海鲜大酒楼', lon: 116.3985, lat: 39.9023, info: '每日新鲜海鲜，粤式烹饪' },
          { name: '意大利餐厅', lon: 116.4135, lat: 39.9198, info: '正宗意大利面和披萨' },
          { name: '日式料理', lon: 116.3921, lat: 39.9076, info: '寿司、刺身和日式烧烤' }
        ]
      },
      {
        id: 'parks',
        name: '公园',
        color: '#2ed573',
        visible: true,
        features: [
          { name: '颐和园', lon: 116.2715, lat: 39.9997, info: '中国古典园林，世界文化遗产' },
          { name: '天坛公园', lon: 116.4109, lat: 39.8818, info: '明清两代帝王祭天之地' },
          { name: '奥林匹克公园', lon: 116.3953, lat: 40.0023, info: '2008年奥运会主场地' },
          { name: '朝阳公园', lon: 116.4808, lat: 39.9404, info: '北京市四环以内最大的城市公园' },
          { name: '玉渊潭公园', lon: 116.3165, lat: 39.9157, info: '以樱花观赏闻名的城市公园' }
        ]
      },
      {
        id: 'hotels',
        name: '酒店',
        color: '#1e90ff',
        visible: true,
        features: [
          { name: '北京饭店', lon: 116.4089, lat: 39.9108, info: '百年历史，五星级酒店' },
          { name: '长城饭店', lon: 116.4612, lat: 39.9541, info: '国际五星级连锁酒店' },
          { name: '王府井希尔顿', lon: 116.4172, lat: 39.9172, info: '位于王府井商圈的高端酒店' },
          { name: '四合院精品酒店', lon: 116.4005, lat: 39.9398, info: '传统北京四合院改造的特色酒店' },
          { name: '机场希尔顿', lon: 116.6007, lat: 40.0738, info: '首都国际机场附近的五星级酒店' }
        ]
      },
      {
        id: 'malls',
        name: '商场',
        color: '#ffa502',
        visible: true,
        features: [
          { name: '王府井百货', lon: 116.4170, lat: 39.9175, info: '北京最著名的购物中心' },
          { name: '西单大悦城', lon: 116.3781, lat: 39.9132, info: '年轻时尚购物中心' },
          { name: '国贸商城', lon: 116.4615, lat: 39.9096, info: '高端奢侈品购物中心' },
          { name: '三里屯太古里', lon: 116.4532, lat: 39.9343, info: '潮流时尚与餐饮娱乐综合体' },
          { name: '蓝色港湾', lon: 116.4805, lat: 39.9478, info: '欧式风格购物公园' }
        ]
      },
      {
        id: 'hospitals',
        name: '医院',
        color: '#a55eea',
        visible: true,
        features: [
          { name: '协和医院', lon: 116.4173, lat: 39.9131, info: '中国顶尖综合医院' },
          { name: '301医院', lon: 116.2777, lat: 39.9096, info: '中国人民解放军总医院' },
          { name: '北京儿童医院', lon: 116.3562, lat: 39.8995, info: '全国最好的儿童专科医院' },
          { name: '中日友好医院', lon: 116.4228, lat: 39.9667, info: '中日两国合作建设的综合医院' },
          { name: '北京大学人民医院', lon: 116.3687, lat: 39.8816, info: '百年历史的著名三甲医院' }
        ]
      }
    ];

    // 创建图层并添加到地图
    const vectorLayers = [];

    layerGroups.forEach(group => {
      const features = group.features.map(feature => {
        const point = new ol.Feature({
          geometry: new ol.geom.Point(
            ol.proj.fromLonLat([feature.lon, feature.lat])
          ),
          name: feature.name,
          type: group.name,
          info: feature.info,
          color: group.color
        });

        // 设置样式
        point.setStyle(new ol.style.Style({
          image: new ol.style.Circle({
            radius: 8,
            fill: new ol.style.Fill({ color: group.color }),
            stroke: new ol.style.Stroke({
              color: '#fff',
              width: 2
            })
          }),
          text: new ol.style.Text({
            text: feature.name,
            offsetY: -20,
            font: 'bold 12px sans-serif',
            fill: new ol.style.Fill({ color: '#fff' }),
            stroke: new ol.style.Stroke({
              color: 'rgba(0,0,0,0.7)',
              width: 3
            })
          })
        }));

        return point;
      });

      const source = new ol.source.Vector({
        features: features
      });

      const layer = new ol.layer.Vector({
        source: source,
        visible: group.visible
      });

      map.addLayer(layer);
      vectorLayers.push({
        id: group.id,
        name: group.name,
        color: group.color,
        layer: layer,
        count: features.length
      });
    });

    // 生成图层控制UI
    const layerControls = document.getElementById('layer-controls');

    vectorLayers.forEach(layer => {
      const layerItem = document.createElement('div');
      layerItem.className = 'layer-item';
      layerItem.innerHTML = `
                <input type="checkbox" id="${layer.id}" ${layer.layer.getVisible() ? 'checked' : ''}>
                <div class="layer-color" style="background-color: ${layer.color};"></div>
                <div class="layer-info">
                    <div class="layer-name">${layer.name}</div>
                    <div class="layer-count">${layer.count} 个点位</div>
                </div>
            `;

      const checkbox = layerItem.querySelector('input');
      checkbox.addEventListener('change', function () {
        layer.layer.setVisible(this.checked);
      });

      layerControls.appendChild(layerItem);
    });

    // 添加点位点击事件
    map.on('click', function (evt) {
      const featureInfo = document.getElementById('feature-info');
      map.forEachFeatureAtPixel(evt.pixel, function (feature) {
        const properties = feature.getProperties();
        const coords = ol.proj.toLonLat(
          feature.getGeometry().getCoordinates()
        );

        featureInfo.innerHTML = `
                    <h3>${properties.name}</h3>
                    <p><strong>类型:</strong> ${properties.type}</p>
                    <p><strong>坐标:</strong> ${coords[0].toFixed(6)}, ${coords[1].toFixed(6)}</p>
                    <p><strong>描述:</strong> ${properties.info}</p>
                `;
        return true; // 只显示第一个匹配的要素
      });
    });

    // 添加悬停效果
    map.on('pointermove', function (evt) {
      const pixel = map.getEventPixel(evt.originalEvent);
      const hit = map.hasFeatureAtPixel(pixel);
      map.getTargetElement().style.cursor = hit ? 'pointer' : '';
    });

    // 添加缩放控制
    map.addControl(new ol.control.Zoom());
  </script>
</body>

</html>